// Copyright Epic Games, Inc. All Rights Reserved.

#include "httpparser.h"

#include <zencore/except.h>
#include <zencore/logging.h>
#include <zencore/string.h>

namespace zen {

using namespace std::literals;

static constinit uint32_t HashContentLength = HashStringAsLowerDjb2("Content-Length"sv);
static constinit uint32_t HashContentType	= HashStringAsLowerDjb2("Content-Type"sv);
static constinit uint32_t HashAccept		= HashStringAsLowerDjb2("Accept"sv);
static constinit uint32_t HashExpect		= HashStringAsLowerDjb2("Expect"sv);
static constinit uint32_t HashSession		= HashStringAsLowerDjb2("UE-Session"sv);
static constinit uint32_t HashRequest		= HashStringAsLowerDjb2("UE-Request"sv);
static constinit uint32_t HashRange			= HashStringAsLowerDjb2("Range"sv);

//////////////////////////////////////////////////////////////////////////
//
// HttpRequestParser
//

http_parser_settings HttpRequestParser::s_ParserSettings{
	.on_message_begin = [](http_parser* p) { return GetThis(p)->OnMessageBegin(); },
	.on_url			  = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnUrl(Data, ByteCount); },
	.on_status =
		[](http_parser* p, const char* Data, size_t ByteCount) {
			ZEN_UNUSED(p, Data, ByteCount);
			return 0;
		},
	.on_header_field	 = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeader(Data, ByteCount); },
	.on_header_value	 = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnHeaderValue(Data, ByteCount); },
	.on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); },
	.on_body			 = [](http_parser* p, const char* Data, size_t ByteCount) { return GetThis(p)->OnBody(Data, ByteCount); },
	.on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); },
	.on_chunk_header{},
	.on_chunk_complete{}};

HttpRequestParser::HttpRequestParser(HttpRequestParserCallbacks& Connection) : m_Connection(Connection)
{
	http_parser_init(&m_Parser, HTTP_REQUEST);
	m_Parser.data = this;

	ResetState();
}

HttpRequestParser::~HttpRequestParser()
{
}

size_t
HttpRequestParser::ConsumeData(const char* InputData, size_t DataSize)
{
	const size_t ConsumedBytes = http_parser_execute(&m_Parser, &s_ParserSettings, InputData, DataSize);

	http_errno HttpErrno = HTTP_PARSER_ERRNO((&m_Parser));

	if (HttpErrno && HttpErrno != HPE_INVALID_EOF_STATE)
	{
		ZEN_WARN("HTTP parser error {} ('{}'). Closing connection", http_errno_name(HttpErrno), http_errno_description(HttpErrno));
		return ~0ull;
	}
	return ConsumedBytes;
}

int
HttpRequestParser::OnUrl(const char* Data, size_t Bytes)
{
	if (!m_Url)
	{
		ZEN_ASSERT_SLOW(m_UrlLength == 0);
		m_Url = m_HeaderCursor;
	}

	const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor;

	if (RemainingBufferSpace < Bytes)
	{
		ZEN_WARN("HTTP parser does not have enough space for incoming request, need {} more bytes", Bytes - RemainingBufferSpace);
		return 1;
	}

	memcpy(m_HeaderCursor, Data, Bytes);
	m_HeaderCursor += Bytes;
	m_UrlLength += Bytes;

	return 0;
}

int
HttpRequestParser::OnHeader(const char* Data, size_t Bytes)
{
	if (m_CurrentHeaderValueLength)
	{
		AppendCurrentHeader();

		m_CurrentHeaderNameLength  = 0;
		m_CurrentHeaderValueLength = 0;
		m_CurrentHeaderName		   = m_HeaderCursor;
	}
	else if (m_CurrentHeaderName == nullptr)
	{
		m_CurrentHeaderName = m_HeaderCursor;
	}

	const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor;
	if (RemainingBufferSpace < Bytes)
	{
		ZEN_WARN("HTTP parser does not have enough space for incoming header name, need {} more bytes", Bytes - RemainingBufferSpace);
		return 1;
	}

	memcpy(m_HeaderCursor, Data, Bytes);
	m_HeaderCursor += Bytes;
	m_CurrentHeaderNameLength += Bytes;

	return 0;
}

void
HttpRequestParser::AppendCurrentHeader()
{
	std::string_view HeaderName(m_CurrentHeaderName, m_CurrentHeaderNameLength);
	if (m_Headers.size() == std::numeric_limits<int8_t>::max())
	{
		ZEN_WARN("HttpRequestParser parser only supports up to {} headers, can't store header '{}'. Dropping it.",
				 std::numeric_limits<int8_t>::max(),
				 HeaderName);
		return;
	}
	std::string_view HeaderValue(m_CurrentHeaderValue, m_CurrentHeaderValueLength);

	const uint32_t HeaderHash = HashStringAsLowerDjb2(HeaderName);

	if (HeaderHash == HashContentLength)
	{
		m_ContentLengthHeaderIndex = (int8_t)m_Headers.size();
	}
	else if (HeaderHash == HashAccept)
	{
		m_AcceptHeaderIndex = (int8_t)m_Headers.size();
	}
	else if (HeaderHash == HashContentType)
	{
		m_ContentTypeHeaderIndex = (int8_t)m_Headers.size();
	}
	else if (HeaderHash == HashSession)
	{
		m_SessionId = Oid::TryFromHexString(HeaderValue);
	}
	else if (HeaderHash == HashRequest)
	{
		std::from_chars(HeaderValue.data(), HeaderValue.data() + HeaderValue.size(), m_RequestId);
	}
	else if (HeaderHash == HashExpect)
	{
		if (HeaderValue == "100-continue"sv)
		{
			// We don't currently do anything with this
			m_Expect100Continue = true;
		}
		else
		{
			ZEN_INFO("Unexpected expect - Expect: {}", HeaderValue);
		}
	}
	else if (HeaderHash == HashRange)
	{
		m_RangeHeaderIndex = (int8_t)m_Headers.size();
	}

	m_Headers.emplace_back(HeaderName, HeaderValue);
}

int
HttpRequestParser::OnHeaderValue(const char* Data, size_t Bytes)
{
	if (m_CurrentHeaderValueLength == 0)
	{
		m_CurrentHeaderValue = m_HeaderCursor;
	}

	const size_t RemainingBufferSpace = sizeof m_HeaderBuffer + m_HeaderBuffer - m_HeaderCursor;
	if (RemainingBufferSpace < Bytes)
	{
		ZEN_WARN("HTTP parser does not have enough space for incoming header value, need {} more bytes", Bytes - RemainingBufferSpace);
		return 1;
	}

	memcpy(m_HeaderCursor, Data, Bytes);
	m_HeaderCursor += Bytes;
	m_CurrentHeaderValueLength += Bytes;

	return 0;
}

static void
NormalizeUrlPath(const char* Url, size_t UrlLength, std::string& NormalizedUrl)
{
	bool LastCharWasSeparator = false;
	for (std::string_view::size_type UrlIndex = 0; UrlIndex < UrlLength; ++UrlIndex)
	{
		const char UrlChar	   = Url[UrlIndex];
		const bool IsSeparator = (UrlChar == '/');

		if (IsSeparator && LastCharWasSeparator)
		{
			if (NormalizedUrl.empty())
			{
				NormalizedUrl.reserve(UrlLength);
				NormalizedUrl.append(Url, UrlIndex);
			}

			if (!LastCharWasSeparator)
			{
				NormalizedUrl.push_back('/');
			}
		}
		else if (!NormalizedUrl.empty())
		{
			NormalizedUrl.push_back(UrlChar);
		}

		LastCharWasSeparator = IsSeparator;
	}
}

int
HttpRequestParser::OnHeadersComplete()
{
	try
	{
		if (m_CurrentHeaderValueLength)
		{
			AppendCurrentHeader();
		}

		m_KeepAlive = !!http_should_keep_alive(&m_Parser);

		switch (m_Parser.method)
		{
			case HTTP_GET:
				m_RequestVerb = HttpVerb::kGet;
				break;

			case HTTP_POST:
				m_RequestVerb = HttpVerb::kPost;
				break;

			case HTTP_PUT:
				m_RequestVerb = HttpVerb::kPut;
				break;

			case HTTP_DELETE:
				m_RequestVerb = HttpVerb::kDelete;
				break;

			case HTTP_HEAD:
				m_RequestVerb = HttpVerb::kHead;
				break;

			case HTTP_COPY:
				m_RequestVerb = HttpVerb::kCopy;
				break;

			case HTTP_OPTIONS:
				m_RequestVerb = HttpVerb::kOptions;
				break;

			default:
				ZEN_WARN("invalid HTTP method: '{}'", http_method_str((http_method)m_Parser.method));
				break;
		}

		std::string_view Url(m_Url, m_UrlLength);

		if (auto QuerySplit = Url.find_first_of('?'); QuerySplit != std::string_view::npos)
		{
			m_UrlLength	  = QuerySplit;
			m_QueryString = m_Url + QuerySplit + 1;
			m_QueryLength = Url.size() - QuerySplit - 1;
		}

		NormalizeUrlPath(m_Url, m_UrlLength, m_NormalizedUrl);

		if (m_ContentLengthHeaderIndex >= 0)
		{
			std::string_view& Value			= m_Headers[m_ContentLengthHeaderIndex].Value;
			uint64_t		  ContentLength = 0;
			std::from_chars(Value.data(), Value.data() + Value.size(), ContentLength);

			if (ContentLength)
			{
				m_BodyBuffer = IoBuffer(ContentLength);
			}

			m_BodyBuffer.SetContentType(ContentType());

			m_BodyPosition = 0;
		}
	}
	catch (const std::exception& Ex)
	{
		ZEN_WARN("HttpRequestParser::OnHeadersComplete failed. Reason '{}'", Ex.what());
		return -1;
	}
	return 0;
}

int
HttpRequestParser::OnBody(const char* Data, size_t Bytes)
{
	if (m_BodyPosition + Bytes > m_BodyBuffer.Size())
	{
		ZEN_WARN("HTTP parser incoming body is larger than content size, need {} more bytes",
				 (m_BodyPosition + Bytes) - m_BodyBuffer.Size());
		return 1;
	}
	memcpy(reinterpret_cast<uint8_t*>(m_BodyBuffer.MutableData()) + m_BodyPosition, Data, Bytes);
	m_BodyPosition += Bytes;

	if (http_body_is_final(&m_Parser))
	{
		if (m_BodyPosition != m_BodyBuffer.Size())
		{
			ZEN_WARN("Body mismatch! {} != {}", m_BodyPosition, m_BodyBuffer.Size());
			return 1;
		}
	}

	return 0;
}

void
HttpRequestParser::ResetState()
{
	m_HeaderCursor			   = m_HeaderBuffer;
	m_CurrentHeaderName		   = nullptr;
	m_CurrentHeaderNameLength  = 0;
	m_CurrentHeaderValue	   = nullptr;
	m_CurrentHeaderValueLength = 0;
	m_Url					   = nullptr;
	m_UrlLength				   = 0;
	m_QueryString			   = nullptr;
	m_QueryLength			   = 0;
	m_ContentLengthHeaderIndex = -1;
	m_AcceptHeaderIndex		   = -1;
	m_ContentTypeHeaderIndex   = -1;
	m_RangeHeaderIndex		   = -1;
	m_Expect100Continue		   = false;
	m_BodyBuffer			   = {};
	m_BodyPosition			   = 0;
	m_Headers.clear();
	m_NormalizedUrl.clear();
}

int
HttpRequestParser::OnMessageBegin()
{
	return 0;
}

int
HttpRequestParser::OnMessageComplete()
{
	try
	{
		m_Connection.HandleRequest();
		ResetState();
		return 0;
	}
	catch (const AssertException& AssertEx)
	{
		ZEN_WARN("Assert caught when processing http request: {}", AssertEx.FullDescription());
		return 1;
	}
	catch (const std::system_error& SystemError)
	{
		if (IsOOM(SystemError.code()))
		{
			ZEN_WARN("out of memory when processing http request: '{}'", SystemError.what());
		}
		else if (IsOOD(SystemError.code()))
		{
			ZEN_WARN("out of disk space when processing http request: '{}'", SystemError.what());
		}
		else
		{
			ZEN_ERROR("failed processing http request: '{}'", SystemError.what());
		}
		ResetState();
		return 1;
	}
	catch (const std::bad_alloc& BadAlloc)
	{
		ZEN_WARN("out of memory when processing http request: '{}'", BadAlloc.what());
		ResetState();
		return 1;
	}
	catch (const std::exception& Ex)
	{
		ZEN_ERROR("failed processing http request: '{}'", Ex.what());
		ResetState();
		return 1;
	}
}

}  // namespace zen
